#include <stdio.h>
#include <iostream>
#include <time.h>
#include "ParameterLearning.h"
#include "ParameterLearningFactory.h"
#include "StructureLearning.h"
#include "StructureLearningFactory.h"
#include "InferenceFactory.h"
#include "Inference.h"
#include "Environment.h"
#include "DataSet.h"
#include "AllFeatureClasses.h"
#include "AllStructureLearningClasses.h"
#include "Feature.h"
#include "FeatureFactory.h"
#include "PGMStruct.h"

#define	defaultConfigFile	"../PGMStructureLearning.conf"

int main(int argc, char *argv[]) {
	// Setting parameters of random function 'rand'
	srand ( (unsigned int) time(NULL) );
	// Reading configuration parameters
	Environment environment;
	environment.readConfigFile(defaultConfigFile);
	// Creating object for data set and reading info from file (whose address is in environment)
	DataSet dataSet;
	dataSet.readDataSet (environment);
	
	// Creating a PGM and initializing it according to data set
	PGMStruct pgmStruct;
	pgmStruct.initStructure (dataSet);

	// Splitting the data set into train and test data sets
	double prop;
	if (environment.getDoubleParamValue(prop, "Proportion") != 0) {
		std::cout << "Error in main file: there is no 'proportion' parameter in the data set.\n";
		return -1;
	}
	DataSet dataSetTest;
	dataSet.divideIntoTwo(dataSetTest, prop);

	/*
	ParameterLearningFactory plFactory;
	ParameterLearning* plClass;
	// Creating a specific parameter learning class
	std::string plName;
	if (environment.getStringParamValue(plName, "AlgorithmForParameterLearning") != 0)
		return -1;
	plClass = plFactory.createObjectByName(plName);
	delete plClass;	
	*/
	
	// Structure learning given the data set
	StructureLearningFactory slFactory;
	StructureLearning* slClass;
	// Creating a specific parameter learning class
	std::string slName;
	if (environment.getStringParamValue(slName, "AlgorithmForStructureLearning") != 0) {
		std::cout << "Error in main file: there is no specification of what structure learning algorithm to use.\n";
		return -1;
	}
	slClass = slFactory.createObjectByName(slName);
	slClass->setParameters(environment);

	time_t seconds;
	seconds = time (NULL);
	slClass->learn(dataSet, pgmStruct);
	seconds = time (NULL) - seconds;

	std::cout << "\n\tTime required: " << seconds / 3600 << " hours " << (seconds / 60) % 60 << " min " << seconds % 60 << " sec.\n";

	// To see the dynamics on the test data set:
	std::cout << "\nLog-loss on the testing data set is: ";
	std::string valStr;
	Inference* inference;
	if (environment.getStringParamValue(valStr, "AlgorithmForRunningInference") == 0) {
		InferenceFactory inferenceFactory;
		inference = inferenceFactory.createObjectByName (valStr);
		inference->setParameters(environment);
	} else {
		std::cout << "\nError in main-file: cannot find specification of inference algorithm in the config file.\n";
	}
	float logLikelihoodTest, logLikelihoodTrain, logZ;
	inference->run(logZ, pgmStruct);
	logLikelihoodTest = inference->getLogLikelihood (dataSetTest, pgmStruct);
	std::cout << "\nTest data set log-loss: " << - logLikelihoodTest << "\n";
	logLikelihoodTrain = inference->getLogLikelihood (dataSet, pgmStruct);
	std::cout << "\nTrain data set log-loss: " << - logLikelihoodTrain << "\n";

	// Freeing dynamic variables
	if (slClass != NULL)
		delete slClass;	
	if (inference != NULL)
		delete inference;

	// Saving results to the log-file
	std::string pathStr;
	if (environment.getStringParamValue(pathStr, "PathToSaveResults") == 0) {
		//SAVING CONFIG-FILE
		environment.saveFile("PGMStructureLearning.conf");
		// if path is given - than saving the results
		pathStr.append("/log.txt");
		std::ofstream file;
		file.open(pathStr, std::ofstream::out | std::ofstream::app);
		file << "\nTime spent: " << seconds / 3600 << " hours " << (seconds / 60) % 60 << " min " << seconds % 60 << " sec.\n";
		file << "\nTest data set log-loss: " << - logLikelihoodTest << "\n";
		file << "\nTrain data set log-loss: " << - logLikelihoodTrain << "\n";
		file.close();
	}
	
	// Debug thing - to see the console before closing
	std::cout << "\nPress enter to exit.";
	std::cin.get();

    return 0;
}
